package org.budo.netty.servlet.server;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.Servlet;
import javax.servlet.ServletRegistration;
import javax.servlet.http.HttpServletRequest;

import org.budo.netty.servlet.server.handler.BudoServletHandler;
import org.budo.netty.servlet.server.handler.DefaultBudoServletHandler;
import org.budo.netty.servlet.server.handler.servlet.BudoServletContext;
import org.budo.netty.servlet.server.handler.servlet.ServletResolved;
import org.budo.netty.servlet.server.handler.servlet.dispatcher.BudoNameRequestDispatcherRequest;
import org.budo.netty.servlet.server.handler.servlet.registration.BudoFilterMapping;
import org.budo.netty.servlet.server.handler.servlet.registration.BudoFilterRegistration;
import org.budo.netty.servlet.server.servlet.DefaultServlet;
import org.budo.netty.servlet.util.InstanceFactory;

/**
 * @author li
 */
public class DefaultBudoServletServer extends AbstractBudoServletServer {
    public void start() {
        final BudoServletContext servletContext = (BudoServletContext) this.getServletContext();
        servletContext.init(); // init

        BudoServletHandler servletHandler = new DefaultBudoServletHandler(this);
        this.getConnector().start(servletHandler);

        Runtime.getRuntime().addShutdownHook(new Thread("DefaultBudoServletServer-ShutdownHook") {
            public void run() {
                servletContext.destroy(); // destroy
            }
        });
    }

    public ServletResolved resolveServlet(HttpServletRequest request) {
        String requestURI = request.getRequestURI();

        Collection<? extends ServletRegistration> servletRegistrations = this.getServletContext().getServletRegistrations().values();

        // by name
        if (request instanceof BudoNameRequestDispatcherRequest) {
            String servletName = ((BudoNameRequestDispatcherRequest) request).getName();
            for (ServletRegistration servletRegistration : servletRegistrations) {
                if (servletName.equals(servletRegistration.getName())) {
                    Servlet servletInstance = InstanceFactory.servletInstance(servletRegistration.getClassName(), request.getServletContext());
                    return new ServletResolved(servletInstance);
                }
            }

            if (servletName.equals("default")) {
                return new ServletResolved(DefaultServlet.INSTANCE);
            }
        }

        // by mapping
        for (ServletRegistration servletRegistration : servletRegistrations) {
            ServletResolved servletResolved = this.matchMappings(servletRegistration.getMappings(), requestURI);
            if (null != servletResolved) {
                Servlet servletInstance = InstanceFactory.servletInstance(servletRegistration.getClassName(), request.getServletContext());
                servletResolved.setServletInstance(servletInstance);
                return servletResolved;
            }
        }

        return new ServletResolved(DefaultServlet.INSTANCE);
    }

    public List<Filter> resolveFilter(HttpServletRequest request, DispatcherType dispatcherType) {
        String requestURI = request.getRequestURI();

        List<Filter> matchedFilters = new ArrayList<Filter>();
        Collection<? extends FilterRegistration> filterRegistrations = this.getServletContext().getFilterRegistrations().values();
        for (FilterRegistration filterRegistration : filterRegistrations) { // BudoFilterRegistration
            BudoFilterRegistration budoFilterRegistration = (BudoFilterRegistration) filterRegistration;

            if (this.matchFilterMappings(budoFilterRegistration.getFilterMappings(), requestURI, dispatcherType)) {
                Filter filter = InstanceFactory.filterInstance(filterRegistration.getClassName(), request.getServletContext());
                matchedFilters.add(filter);
            }
        }
        return matchedFilters;
    }

    private boolean matchFilterMappings(List<BudoFilterMapping> filterMappings, String requestURI, DispatcherType dispatcherType) {
        for (BudoFilterMapping filterMapping : filterMappings) {
            if (null != this.matchMappings(Arrays.asList(filterMapping.getUrlPatterns()), requestURI) //
                    && Arrays.binarySearch(filterMapping.getDispatcherTypes(), dispatcherType) >= 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * 请求路径匹配
     * 
     * @see org.eclipse.jetty.http.PathMap#pathMatch(String, String)
     */
    private ServletResolved matchMappings(Collection<String> mappings, String requestUri) {
        for (String mapping : mappings) { // 精确匹配
            if (requestUri.equals(mapping)) {
                return new ServletResolved(); //
            }
        }

        // 路径匹配
        for (String mapping : mappings) {
            if (mapping.startsWith("/") && mapping.endsWith("/*")) {
                String servletPath = mapping.substring(0, mapping.length() - 2);
                if (requestUri.startsWith(servletPath)) {
                    String pathInfo = requestUri.substring(servletPath.length());
                    return new ServletResolved(servletPath, pathInfo);
                }
            }
        }

        // 扩展名匹配
        for (String mapping : mappings) {
            if (mapping.startsWith("*.") //
                    && requestUri.endsWith(mapping.substring(1))) {
                return new ServletResolved();
            }
        }

        // 缺省匹配
        for (String mapping : mappings) {
            if (mapping.equals("/")) {
                return new ServletResolved();
            }
        }

        return null;
    }

    @Override
    public String toString() {
        return super.toString() + ", connector=" + this.getConnector();
    }
}
